﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Dynamic.Utils;
using System.Reflection;
using System.Runtime.CompilerServices;

#if CLR2
namespace Microsoft.Scripting.Ast
#else
namespace System.Linq.Expressions
#endif 
{
    public class TypeMethodCallExpression: Expression, IArgumentProvider
    {
        private readonly MethodInfo _method;

        internal TypeMethodCallExpression(MethodInfo method)
        {
            _method = method;
        }

        internal virtual IList<Type> ParamTypes
        {
            get { return null; }
        }

        internal virtual Expression GetInstance()
        {
            return null;
        }

        /// <summary>
        /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
        /// </summary>
        /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
        public sealed override ExpressionType NodeType
        {
            get { return ExpressionType.TypeMethodCall; }
        }

        /// <summary>
        /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.)
        /// </summary>
        /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
        public sealed override Type Type
        {
            get { return _method.ReturnType?? typeof(void); }
        }

        /// <summary>
        /// Gets the <see cref="MethodInfo" /> for the method to be called.
        /// </summary>
        public MethodInfo Method
        {
            get { return _method; }
        }

        /// <summary>
        /// Gets the <see cref="Expression" /> that represents the instance 
        /// for instance method calls or null for static method cals.
        /// </summary>
        public Expression Object
        {
            get { return GetInstance(); }
        }

        public virtual Type ObjectType
        {
            get { return null; }
        }

        /// <summary>
        /// Gets a collection of expressions that represent arguments to the method call.
        /// </summary>
        public ReadOnlyCollection<Expression> Arguments
        {
            get { return GetOrMakeArguments(); }
        }

        /// <summary>
        /// Creates a new expression that is like this one, but using the
        /// supplied children. If all of the children are the same, it will
        /// return this expression.
        /// </summary>
        /// <param name="object">The <see cref="Object" /> property of the result.</param>
        /// <param name="arguments">The <see cref="Arguments" /> property of the result.</param>
        /// <param name="paramTypes">The <see cref="ParamTypes" /> The input types of the parameters for the method.</param>
        /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
        public TypeMethodCallExpression Update(Expression @object, IEnumerable<Expression> arguments, IList<Type> paramTypes)
        {
            if (@object == Object && arguments == Arguments)
            {
                return this;
            }
            return Expression.TypeMethodCall(@object, Method, arguments, paramTypes);
        }

        internal virtual ReadOnlyCollection<Expression> GetOrMakeArguments()
        {
            throw ContractUtils.Unreachable;
        }

        /// <summary>
        /// Dispatches to the specific visit method for this node type.
        /// </summary>
        protected internal override Expression Accept(ExpressionVisitor visitor)
        {
            return visitor.VisitTypeMethodCall(this);
        }

        /// <summary>
        /// Returns a new MethodCallExpression replacing the existing instance/args with the
        /// newly provided instance and args.    Arguments can be null to use the existing
        /// arguments.
        /// 
        /// This helper is provided to allow re-writing of nodes to not depend on the specific optimized
        /// subclass of MethodCallExpression which is being used. 
        /// </summary>
        internal virtual TypeMethodCallExpression Rewrite(Expression type, IList<Expression> args, IList<Type> paramTypes)
        {
            throw ContractUtils.Unreachable;
        }

        #region IArgumentProvider Members

        public Expression GetArgument(int index)
        {
            throw ContractUtils.Unreachable;
        }

        public int ArgumentCount
        {
            get { throw ContractUtils.Unreachable; }
        }

        #endregion
    }

    internal class TypeInstanceMethodCallExpressionN : TypeMethodCallExpression, IArgumentProvider
    {
        private IList<Expression> _arguments;
        private IList<Type> _paramTypes;
        private readonly Type _type;
        private readonly Expression _instance;

        public TypeInstanceMethodCallExpressionN(MethodInfo method, Type type, IList<Expression> args, IList<Type> paramTypes)
            : base(method)
        {
            ContractUtils.RequiresNotNull(type, "type");
            _type = type;
            _arguments = args;
            _paramTypes = paramTypes;
        }

        public TypeInstanceMethodCallExpressionN(Expression instance,MethodInfo method, IList<Expression> args, IList<Type> paramTypes)
            : base(method)
        {
            _type = instance.Type;
            _instance = instance;
            _arguments = args;
            _paramTypes = paramTypes;
        }

        internal override IList<Type> ParamTypes{
            get{return _paramTypes;}
        }

        Expression IArgumentProvider.GetArgument(int index)
        {
            return _arguments[index];
        }

        int IArgumentProvider.ArgumentCount
        {
            get
            {
                return _arguments.Count;
            }
        }

        internal override Expression GetInstance()
        {
            return _instance;
        }

        public override Type ObjectType{get{return _type;}}

        internal override ReadOnlyCollection<Expression> GetOrMakeArguments()
        {
            return ReturnReadOnly(ref _arguments);
        }

        internal override TypeMethodCallExpression Rewrite(Expression instance, IList<Expression> args, IList<Type> paramTypes)
        {
            Debug.Assert(instance != null);
            Debug.Assert(args == null || args.Count == _arguments.Count);

            return Expression.TypeMethodCall(instance, Method, args ?? _arguments, paramTypes ?? _paramTypes);
        }
    }


    public partial class Expression
    {

        #region TypeMethodCall

        public static TypeMethodCallExpression TypeMethodCall(Type type, MethodInfo method, Type[] paramTypes,params Expression[] arguments)
        {
            return TypeMethodCall(type, method, (IEnumerable<Expression>)arguments, (IList<Type>)paramTypes);
        }

        public static TypeMethodCallExpression TypeMethodCall(Expression instance, MethodInfo method, Type[] paramTypes, params Expression[] arguments)
        {
            return TypeMethodCall(instance, method, (IEnumerable<Expression>)arguments, (IList<Type>)paramTypes);
        }

        public static TypeMethodCallExpression TypeMethodCall(Type type, MethodInfo method, IEnumerable<Expression> arguments, IList<Type> paramTypes)
        {
            ContractUtils.RequiresNotNull(method,"method");

            ReadOnlyCollection<Expression> argList = arguments.ToReadOnly();

            return new TypeInstanceMethodCallExpressionN(method, type, argList, paramTypes);
        }

        public static TypeMethodCallExpression TypeMethodCall(Expression instance, MethodInfo method, IEnumerable<Expression> arguments, IList<Type> paramTypes)
        {
            ContractUtils.RequiresNotNull(method, "method");

            ReadOnlyCollection<Expression> argList = arguments.ToReadOnly();

            return new TypeInstanceMethodCallExpressionN(instance,method, argList, paramTypes);
        }
        #endregion
    }
}
